{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<style>div.container { width: 100% }</style>\n",
    "<img style=\"float:left;  vertical-align:text-bottom;\" height=\"65\" width=\"172\" src=\"../assets/PyViz_logo_wm_line.png\" />\n",
    "<div style=\"float:right; vertical-align:text-bottom;\"><h2>Tutorial 05. Working with Gridded Datasets</h2></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Many datasets in science and engineering consist of n-dimensional data. Gridded datasets usually represent observations of some continuous variable across multiple dimensions---a monochrome image representing luminance values across a 2D surface, volumetric 3D data, an RGB image sequence over time, or any other multi-dimensional parameter space. This type of data is particularly common in research areas that make use of spatial imaging or modeling, such as climatology, biology, and astronomy, but can also be used to represent any arbitrary data that varies over multiple dimensions.\n",
    "\n",
    "For gridded data, we'll use xarray, a convenient way of working with and representing labelled n-dimensional arrays, like pandas for labelled n-D arrays, along with our other usual libraries:\n",
    "\n",
    "<div style=\"margin: 10px\">\n",
    "<a href=\"http://holoviews.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/holoviews.png\"/></a>\n",
    "<a href=\"http://bokeh.pydata.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/bokeh.png\"/></a>\n",
    "<a href=\"http://xarray.pydata.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/xarray_wm.png\"/></a>\n",
    "<a href=\"http://numpy.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/numpy.png\"/></a>\n",
    "<a href=\"http://pandas.pydata.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:130px\" src=\"../assets/pandas.png\"/></a>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import holoviews as hv\n",
    "import xarray as xr\n",
    "hv.extension('bokeh')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mri_xr = xr.open_dataset('../data/mri.nc')\n",
    "mri_xr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "xarray is particularly useful for geographic data, but it supports any type of n-dimensional data. The data here represents volumetric data from an [MRI scan](https://graphics.stanford.edu/data/voldata/), with three coordinate dimensions 'x', 'y' and 'z'. In this simple example these coordinates are integers, but they are not required to be. Instead of volumetric data, we could imagine the data could be 2D spatial data that evolves over time, as is common in climatology and many other fields."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Declaring the dataset\n",
    "\n",
    "Unlike with Pandas, in a gridded dataset the dimensions are typically already declared unambiguously, with **coordinates** (i.e. key dimensions) and **data variables** (i.e. value dimensions) that HoloViews can determine automatically:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mri = hv.Dataset(mri_xr)\n",
    "mri"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Displaying the data\n",
    "\n",
    "Just as we saw in the previous tutorial, we can group the data by one or more dimensions. Since we are dealing with volumetric data but have only a 2D display device, we can take slices along each axis. Here we will slice along the sagittal plane corresponding to the z-dimension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mri.to(hv.Image, groupby='z', dynamic=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we supplied ``dynamic=True`` to get a [``DynamicMap``](http://holoviews.org/reference/containers/bokeh/DynamicMap.html), which works like a [``HoloMap``](http://holoviews.org/reference/containers/bokeh/HoloMap.html) but computes each frame on demand, so that it only creates each plot when it is being used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise: Display transverse (x,z) or frontal (z,y) sections of the data by declaring the kdims in the .to method\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Slice and dice across n dimensions\n",
    "\n",
    "We can use ``.to`` to slice the cube along all three axes separately:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Image [xaxis=None yaxis=None width=225 height=225]\n",
    "(mri.to(hv.Image, ['z', 'y'], dynamic=True) +\n",
    " mri.to(hv.Image, ['z', 'x'], dynamic=True) +\n",
    " mri.to(hv.Image, ['x', 'y'], dynamic=True)).redim.range(MR=(0, 255))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Aggregation\n",
    "\n",
    "We can also easily compute aggregates across one or more dimensions. Previously we used the ``aggregate`` method for this purpose, but when working with gridded datasets it often makes more sense to think of aggregation as a ``reduce`` operation. We can for example reduce the ``z`` dimension using ``np.mean`` and display the resulting averaged 2D array as an [``Image``](http://holoviews.org/reference/elements/bokeh/Image.html):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.Image(mri.reduce(z=np.mean))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise: Recreate the plot above using the aggregate method\n",
    "# Hint: The aggregate and reduce methods are inverses of each other\n",
    "# Try typing \"hv.Image(mri.aggregate(\" and press shift-Tab to see the signature of `aggregate`.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you keep a handle on your image, you can use HoloViews to generate the aggregate array by accessing ``.data``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "im = hv.Image(mri.reduce(z=np.mean))\n",
    "im.data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, it is straightforward to work with the additional dimensions of data available in gridded datasets, as explained in more detail in the [user guide](http://holoviews.org/user_guide/Gridded_Datasets.html).\n",
    "\n",
    "# Onwards\n",
    "\n",
    "The previous sections focused on displaying plots that provide certain standard types of interactivity, whether widget-based (to select values along a dimension) or within each plot (for panning, zooming, etc.).  A wide range of additional types of interactivity can also be defined by the user for working with specific types of data, as outlined in the following sections, beginning with [Network Graphs](./06_Network_Graphs.ipynb)."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
